<?php
/**
 * Created by PhpStorm.
 * User: LuLingFeng
 * Date: 2017/12/27
 * Time: 13:20
 */

namespace App\Http\Admin\Personnel\Models;

use App\Eloquent\Oa\AttendanceExceptionDate;
use App\Eloquent\Oa\AttendanceRule;
use App\Eloquent\Oa\AttendanceStatistics;
use App\Eloquent\Oa\Contacts;
use Carbon\Carbon;
use Framework\BaseClass\Http\Admin\Model;

class Attendance extends Model
{
    const ATTENDANCE_STATISTICS_TYPE_DAY = 3;
    const ATTENDANCE_STATISTICS_TYPE_MONTH = 2;
    const ATTENDANCE_STATISTICS_TYPE_YEAR = 1;

    const MAX_INT = 2147483647;

    public function statisticsList($where, $paging)
    {
        // 获取(分页)员工id
        $stat = Contacts::orderBy('created_at', 'desc');
        if (!empty($where['company_id'])) {
            $stat->where('oa_company_id', '=', $where['company_id']);
        }
        if (!empty($where['name'])) {
            $stat->where('name', '=', $where['name']);
        }
        $totalRow = $stat->count();
        $stat->forPage($paging['page'], $paging['page_size']);
        $contactsList = $stat->orderBy('oa_department_id', 'desc')
            ->get();
        $idList = $contactsList->pluck('id')->values();
        $departmentIdList = $contactsList->unique('oa_department_id')->pluck('oa_department_id')->values();

        // 时间
        if (empty($where['start_time']) || empty($where['end_time'])) xThrow(ERR_PARAMETER, '请设置开始和结束时间');
        $startTime = is_numeric($where['start_time']) ? $where['start_time'] : strtotime($where['start_time']);
        $endTime = is_numeric($where['end_time']) ? $where['end_time'] : strtotime($where['end_time']);
        $endTime += 86400; // 例: 01-31 实际时间1-31 24点
//        $time = is_numeric($where['time']) ? $where['time'] : strtotime($where['time']);
//        $startTime = $time;
//        $endTime = Carbon::createFromTimestamp($time)->addMonth(1)->timestamp;
//        debug($where['time'], date('Y-m-d',$startTime), date('Y-m-d',$endTime));

        // 初始化
        $stat = AttendanceStatistics::with('contactsInfo');

        // 筛选
        $stat->whereIn('oa_contacts_id', $idList);  // 筛选员工id
        $stat->where('type', '=', self::ATTENDANCE_STATISTICS_TYPE_DAY); // 日统计
//        $stat->where('time', '=', $time);   // 指定月份
        // 指定时间段
        $stat->where('time', '>=', $startTime);
        $stat->where('time', '<', $endTime);
        $stat->groupBy('oa_contacts_id', 'oa_department_id', 'oa_company_id');
        $list = $stat->get([\DB::raw("
            sum(clock_in_on_time) as clock_in_on_time, sum(clock_in_late) as clock_in_late, sum(clock_in_absence) as clock_in_absence,
            sum(clock_out_on_time) as clock_out_on_time, sum(clock_out_early) as clock_out_early, sum(personal_leave) as personal_leave,
            sum(medical_leave) as medical_leave,
            sum(field_check_in) as field_check_in, sum(patch_check_in) as patch_check_in,
            count(1) as check_in_days,
            oa_contacts_id, oa_department_id, oa_company_id
        ")]);

        foreach ($contactsList as $contacts) {
            if (!$list->contains('oa_contacts_id', '=', $contacts['id'])) {
                $d = new AttendanceStatistics([
                    'oa_contacts_id'    => $contacts['id'],
                    'oa_department_id'  => $contacts['oa_department_id'],
                    'oa_company_id'     => $contacts['oa_company_id'],
                    "clock_in_on_time"  => 0,
                    "clock_in_late"     => 0,
                    "clock_in_absence"  => 0,
                    "clock_out_on_time" => 0,
                    "clock_out_early"   => 0,
                    "field_check_in"    => 0,
                    "patch_check_in"    => 0,
                    "medical_leave"     => 0,
                    "personal_leave"    => 0,
                    "medical_leave"     => 0,
                ]);
                $d['name'] = $contacts['name'];
                $list->add($d);
            }
        }
        $departmentWorkdays = $this->getDaysByDepartmentIdList($departmentIdList, $startTime, $endTime);
        foreach ($list as $item) {
            $item['should_work_days'] = $departmentWorkdays[$item['oa_department_id']]['work_days_count'];
            $item['should_not_work_days'] = $departmentWorkdays[$item['oa_department_id']]['not_work_days_count'];
            if (!isset($item['name'])) {
                $item['name'] = $item['contactsInfo']['name'];
                unset($item['contactsInfo']);
            }
            $item['start_time'] = $startTime;
            $item['end_time'] = $endTime;

            // 漏打次数
            $missedClockIn = $item['should_work_days'] - $item['clock_in_on_time'] - $item['clock_in_late']
                - $item['clock_in_absence'];
            $missedClockOut = $item['should_work_days'] - $item['clock_out_on_time'] - $item['clock_out_early'];
            $missedCheckIn = $item['should_work_days'] - $item['check_in_days'];
            $item['missed_clock_in'] = $missedClockIn < 0 ? 0 : $missedClockIn;
            $item['missed_clock_out'] = $missedClockOut < 0 ? 0 : $missedClockOut;
            $item['missed_check_in'] = $missedCheckIn < 0 ? 0 : $missedCheckIn;  // 缺卡次数(上下午都没打卡)
        }

        return [
            'list'        => $list,
            'totalRow'    => $totalRow,
            'pageCurrent' => $paging['page']
        ];
    }

    public function statisticsCount($where, $select = [])
    {
        // 默认select
        if (empty($select)) $select = [\DB::raw("
            sum(clock_in_on_time) as clock_in_on_time, sum(clock_in_late) as clock_in_late, sum(clock_in_absence) as clock_in_absence,
            sum(clock_out_on_time) as clock_out_on_time, sum(clock_out_early) as clock_out_early, sum(personal_leave) as personal_leave,
            sum(medical_leave) as medical_leave
        ")];

        // 时间
        if (empty($where['start_time']) || empty($where['end_time'])) xThrow(ERR_PARAMETER, '请设置开始和结束时间');
        $startTime = is_numeric($where['start_time']) ? $where['start_time'] : strtotime($where['start_time']);
        $endTime = is_numeric($where['end_time']) ? $where['end_time'] : strtotime($where['end_time']);
        $endTime += 86400; // 例: 01-31 实际时间1-31 24点

        // 初始化
        $stat = AttendanceStatistics::with([]);

        // 筛选
        if (!empty($where['oa_company_id'])) $stat->where('oa_company_id', '=', $where['oa_company_id']);
        if (!empty($where['oa_department_id'])) $stat->where('oa_department_id', '=', $where['oa_department_id']);
        $stat->where('type', '=', self::ATTENDANCE_STATISTICS_TYPE_DAY); // 日统计
//        $stat->where('time', '=', $time);   // 指定月份
        // 指定时间段
        $stat->where('time', '>=', $startTime);
        $stat->where('time', '<', $endTime);
        $data = $stat->get($select);
        return $data;
    }

    public function ruleSetUp(
        $companyId, $departmentIds, $checkInDistance, $clockInStartTime, $clockInTime, $clockOutTime, $absenceTime,
        $clockInLastTime, $clockOutLastTime, $isSatWorkday, $isApplyToSubDp = 0, $exceptionDatesWork = [], $exceptionDatesNotWork = []
    )
    {
        $departmentIdList = is_array($departmentIds) ? $departmentIds : explode(',', $departmentIds);
        try {
            \DB::beginTransaction();
            AttendanceRule::where('end_time', '=', self::MAX_INT)
                ->whereIn('oa_department_id', $departmentIdList)
                ->update([
                    'end_time' => Carbon::tomorrow()->timestamp - 1
                ]);
            AttendanceRule::where('start_time', '=', Carbon::tomorrow()->timestamp)
                ->delete();
            $data = [];
            $exceptionData = [];
            $time = time();
            foreach ($departmentIdList as $departmentId) {
                $delStat = AttendanceExceptionDate::where('oa_department_id', '=', $departmentId)
                    ->where('oa_contacts_id', '=', 0)
                    ->where('oa_company_id', $companyId);  //用于清除之前的数据
                $data[] = [
                    'oa_department_id'    => $departmentId,
                    'oa_company_id'       => $companyId,
                    'start_time'          => Carbon::tomorrow()->timestamp,
                    'end_time'            => self::MAX_INT,
                    'check_in_distance'   => $checkInDistance,
                    'clock_in_start_time' => self::strToTime($clockInStartTime),
                    'clock_in_time'       => self::strToTime($clockInTime),
                    'clock_out_time'      => self::strToTime($clockOutTime),
                    'absence_time'        => self::strToTime($absenceTime),
                    'clock_in_last_time'  => self::strToTime($clockInLastTime),
                    'clock_out_last_time' => self::strToTime($clockOutLastTime),
                    'is_sat_workday'      => $isSatWorkday,
                    'created_at'          => $time,
                    'updated_at'          => $time
                ];
                foreach ($exceptionDatesWork as $date) {
                    if (empty($data)) continue;
                    $date = is_numeric($date) ? $date : strtotime($date);
                    $exceptionData[] = [
                        'oa_department_id' => $departmentId,
                        'oa_company_id'    => $companyId,
                        'time'             => $date,
                        'is_workday'       => 1,
                        'created_at'       => $time,
                        'updated_at'       => $time
                    ];
                    (clone $delStat)->where('time', '=', $date)->delete();
                }
                foreach ($exceptionDatesNotWork as $date) {
                    if (empty($data)) continue;
                    $date = is_numeric($date) ? $date : strtotime($date);
                    $exceptionData[] = [
                        'oa_department_id' => $departmentId,
                        'oa_company_id'    => $companyId,
                        'time'             => $date,
                        'is_workday'       => 0,
                        'created_at'       => $time,
                        'updated_at'       => $time
                    ];
                    (clone $delStat)->cloneWithout(['time'])->where('time', '=', $date)->delete();
                }
            }
            AttendanceRule::insert($data);
            AttendanceExceptionDate::insert($exceptionData);
        } catch (\Throwable $t) {
            xThrow(ERR_UNKNOWN, $t->getMessage());
            \DB::rollBack();
        }
        \DB::commit();
    }

    public function ruleCheck($departmentIds)
    {
        $departmentIdList = is_array($departmentIds) ? $departmentIds : explode(',', $departmentIds);
        $ruleList = AttendanceRule::orderBy('created_at', 'desc')
            ->whereIn('oa_department_id', $departmentIdList)->get();
        if ($ruleList->count() === 0) {
            $rule = [
                'is_default'               => '1',
                'is_sat_workday'           => '0',
                'check_in_distance'        => '1000',
                'clock_in_start_time_str'  => '08:00',
                'clock_in_time_str'        => '09:00',
                'absence_time_str'         => '11:00',
                'clock_in_last_time_str'   => '12:00',
                'clock_out_time_str'       => '17:00',
                'clock_out_last_time_str'  => '21:00',
                'exception_dates_work'     => [],
                'exception_dates_not_work' => [],
            ];
        } else {
            $rule = $ruleList[0];
            $rule['is_default'] = 0;
            $rule['clock_in_start_time_str'] = self::timeToStr($rule['clock_in_start_time']);
            $rule['clock_in_time_str'] = self::timeToStr($rule['clock_in_time']);
            $rule['clock_out_time_str'] = self::timeToStr($rule['clock_out_time']);
            $rule['absence_time_str'] = self::timeToStr($rule['absence_time']);
            $rule['clock_in_last_time_str'] = self::timeToStr($rule['clock_in_last_time']);
            $rule['clock_out_last_time_str'] = self::timeToStr($rule['clock_out_last_time']);

            $exceptionDateList = AttendanceExceptionDate::with('departmentInfo')
                ->where('time', '>=', Carbon::tomorrow()->timestamp)
                ->where('oa_contacts_id', '=', 0)
                ->whereIn('oa_department_id', $departmentIdList)
                ->get();
            $exceptionDatesWork = [];
            $exceptionDatesNotWork = [];
            foreach ($exceptionDateList as $date) {
                if ($date['is_workday'] == 1) {
                    if (isset($exceptionDatesWork[$date['time']])) {
                        $exceptionDatesWork[$date['time']]['ids'] .= ',' . $date['id'];
                        $exceptionDatesWork[$date['time']]['oa_department_ids'] .= ',' . $date['oa_department_id'];
                        $exceptionDatesWork[$date['time']]['oa_department_names'] .= ',' . $date['departmentInfo']['name'];
                    } else {
                        $exceptionDatesWork[$date['time']] = [
                            'ids'                 => $date['id'],
                            'time'                => date('Y-m-d', $date['time']),
                            'is_workday'          => $date['is_workday'],
                            'oa_department_ids'   => $date['oa_department_id'],
                            'oa_department_names' => $date['departmentInfo']['name'],
                        ];
                    }
                } else {
                    if (isset($exceptionDatesNotWork[$date['time']])) {
                        $exceptionDatesNotWork[$date['time']]['ids'] .= ',' . $date['id'];
                        $exceptionDatesNotWork[$date['time']]['oa_department_ids'] .= ',' . $date['oa_department_id'];
                        $exceptionDatesNotWork[$date['time']]['oa_department_names'] .= ',' . $date['departmentInfo']['name'];
                    } else {
                        $exceptionDatesNotWork[$date['time']] = [
                            'ids'                 => $date['id'],
                            'time'                => date('Y-m-d', $date['time']),
                            'is_workday'          => $date['is_workday'],
                            'oa_department_ids'   => $date['oa_department_id'],
                            'oa_department_names' => $date['departmentInfo']['name'],
                        ];
                    }
                }
            }
            $rule['exception_dates_work'] = array_values($exceptionDatesWork);
            $rule['exception_dates_not_work'] = array_values($exceptionDatesNotWork);
        }
        return $rule;
    }

    public function deleteExceptionDate($ids)
    {
        $ids = is_array($ids) ? $ids : explode(',', $ids);
        AttendanceExceptionDate::whereIn("id", $ids)->delete();
        return;
    }

    public function getDaysByDepartmentIdList($idList, $startTime, $endTime, $contactsIds = [])
    {
        if (empty($idList)) {
            // 根据通讯录获取
            if (!is_array($contactsIds)) $contactsIds = explode(',', $contactsIds);
            $idList = Contacts::whereIn('id', $contactsIds)->get(['id', 'oa_department_id'])->pluck('oa_department_id');
        }
        // 计算部门应上班时间
        $exceptionDateList = [];
        $ruleList = [];
        $workDaysByDepartment = [];
        foreach ($idList as $id) {
            if (!isset($exceptionDateList[$id])) {
                $exceptionDateList[$id] = AttendanceExceptionDate::where('oa_department_id', '=', $id)
                    ->where('oa_contacts_id', '=', 0)
                    ->where('time', '<=', $endTime)
                    ->where('time', '>=', $startTime)
                    ->orderBy('created_at', 'desc')
                    ->get();
            }
            if (!isset($ruleList[$id])) {
                $whereTime = "(start_time <= {$startTime} and end_time >= {$endTime})";
                $whereTime .= ' or ' . "(start_time >= {$startTime} and end_time <= {$endTime})";
                $whereTime .= ' or ' . "(start_time <= {$startTime} and end_time >= {$startTime})";
                $whereTime .= ' or ' . "(start_time <= {$endTime} and end_time >= {$endTime})";
                $whereClause = "oa_department_id = {$id} and ($whereTime)";
                $ruleList[$id] = AttendanceRule::whereRaw($whereClause)
                    ->orderBy('start_time', 'asc')
                    ->get();
            }
            $date = $startTime;
            while ($date < $endTime) {
                if (!isset($workDaysByDepartment[$id])) {
                    $workDaysByDepartment[$id] = [
                        'days'                => [],
                        'work_days'           => [],
                        'not_work_days'       => [],
                        'work_days_count'     => 0,
                        'not_work_days_count' => 0
                    ];
                }
//                $day = date('d', $date);
                $day = date('Y-m-d', $date);
                // 当月无规则, 视作该日不上班
                if (count($ruleList[$id]) === 0) {
                    $workDaysByDepartment[$id]['days'][$day] = 0;
                    array_push($workDaysByDepartment[$id]['not_work_days'], $day);
                    $workDaysByDepartment[$id]['not_work_days_count']++;
                    $date += 86400;
                    continue;
                }
                $rule = $ruleList[$id]->where('end_time', '>', $date)->values();
                if (count($rule) === 0) {
                    // 该日无规则, 视作不上班
                    $workDaysByDepartment[$id]['days'][$day] = 0;
                    array_push($workDaysByDepartment[$id]['not_work_days'], $day);
                    $workDaysByDepartment[$id]['not_work_days_count']++;
                    $date += 86400;
                    continue;
                } else {
                    $rule = $rule[0];
                }
                // 该日无规则, 视作不上班
                if ($rule['start_time'] > $date) {
                    $workDaysByDepartment[$id]['days'][$day] = 0;
                    array_push($workDaysByDepartment[$id]['not_work_days'], $day);
                    $workDaysByDepartment[$id]['not_work_days_count']++;
                    $date += 86400;
                    continue;
                }
                $dayOfWeek = date('N', $date);
                $except = $exceptionDateList[$id]->where('time', '=', $date)->values();
                if (count($except) != 0) {
                    if ($except[0]['is_workday'] == 1) {
                        $workDaysByDepartment[$id]['days'][$day] = 1;
                        array_push($workDaysByDepartment[$id]['work_days'], $day);
                        $workDaysByDepartment[$id]['work_days_count']++;
                    } else {
                        $workDaysByDepartment[$id]['days'][$day] = 0;
                        array_push($workDaysByDepartment[$id]['not_work_days'], $day);
                        $workDaysByDepartment[$id]['not_work_days_count']++;
                    }
                } else {
                    if ($dayOfWeek <= 5) {
                        $workDaysByDepartment[$id]['days'][$day] = 1;
                        array_push($workDaysByDepartment[$id]['work_days'], $day);
                        $workDaysByDepartment[$id]['work_days_count']++;
                    }
                    if ($dayOfWeek == 6) {
                        if ($rule['is_sat_workday'] == 1) {
                            $workDaysByDepartment[$id]['days'][$day] = 1;
                            array_push($workDaysByDepartment[$id]['work_days'], $day);
                            $workDaysByDepartment[$id]['work_days_count']++;
                        } else {
                            $workDaysByDepartment[$id]['days'][$day] = 0;
                            array_push($workDaysByDepartment[$id]['not_work_days'], $day);
                            $workDaysByDepartment[$id]['not_work_days_count']++;
                        }
                    }
                    if ($dayOfWeek == 7) {
                        $workDaysByDepartment[$id]['days'][$day] = 0;
                        array_push($workDaysByDepartment[$id]['not_work_days'], $day);
                        $workDaysByDepartment[$id]['not_work_days_count']++;
                    }
                }
                $date += 86400;
            }
        }
        return $workDaysByDepartment;
    }

    public static function timeToStr($time)
    {
        return sprintf('%02d', floor($time / 3600)) . ':' . sprintf('%02d', floor($time % 3600 / 60));
    }

    public static function strToTime($str)
    {
        list($hour, $minute) = explode(':', $str);
        return $hour * 3600 + $minute * 60;
    }
}